package com.sixbro.distributed.lock.redis.aspect;

import com.sixbro.distributed.lock.redis.annotation.Distributedlock;
import com.sixbro.distributed.lock.redis.result.ApiResult;
import com.sixbro.distributed.lock.redis.result.ApiResultCode;
import com.sixbro.distributed.lock.redis.service.RedisDistributedLock;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.UUID;

/**
 * <p>
 *  针对加了@Lock的函数进行分布式锁的增强处理
 * </p>
 *
 * @author: Mr.Lu
 * @since: 2021-12-20 11:54
 */
@Slf4j
@Aspect
@Component
public class DistributedLockAspect {

    @Autowired
    private RedisDistributedLock redisDistributedLock;


    /**
     * 切点
     */
    @Pointcut("@annotation(com.sixbro.distributed.lock.redis.annotation.Distributedlock)")
    public void pointcut() {
    }

    /**
     * 利用环绕通知进行处理重复提交问题
     *
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        // 通过 AnnotationUtils.findAnnotation 获取 RateLimiter 注解
        Distributedlock avoidRepeatSubmit = AnnotationUtils.findAnnotation(method, Distributedlock.class);
        /**
         * 获取锁的时间
         */
        long lockSeconds = avoidRepeatSubmit.lockTime();

        //获得request对象
        HttpServletRequest request = httpServletRequest();

        Assert.notNull(request, "request can not null");

        // 此处可以用token或者JSessionId
        String token = request.getHeader(HttpHeaders.AUTHORIZATION);
        String path = request.getServletPath();
        String key = getKey(token, path);
        String clientId = getClientId();

        //锁定多少秒
        boolean isSuccess = redisDistributedLock.setLock(key, clientId, lockSeconds);

        if (isSuccess) {

            log.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);

            // 获取锁成功, 执行进程
            Object result;
            try {
                result = point.proceed();

            } finally {

                //解锁
                redisDistributedLock.releaseLock(key, clientId);
                log.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);

            }

            return result;

        } else {

            // 获取锁失败，认为是重复提交的请求
            log.info("tryLock fail, key = [{}]", key);
            return ApiResult.fail(ApiResultCode.ERROR).setMessage("重复请求，请稍后再试");
        }

    }

    /**
     * 获得request对象
     *
     * @return
     */
    private HttpServletRequest httpServletRequest() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return requestAttributes.getRequest();
    }


    /**
     * 获得请求key
     *
     * @param token
     * @param path
     * @return
     */
    private String getKey(String token, String path) {
        return token + path;
    }

    /**
     * 获得uuid
     *
     * @return
     */
    private String getClientId() {
        return UUID.randomUUID().toString();
    }

}
